/**
 * This file is part of Lineage II Client Data Parser.
 * 
 * Lineage II Client Data Parser is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Lineage II Client Data Parser is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Lineage II Client Data Parser.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.l2emuproject.parser.base;

import net.l2emuproject.Launcher;
import net.l2emuproject.VersionService;
import net.l2emuproject.io.SafelyPackedFile;
import net.l2emuproject.redist.CustomLogger;
import javolution.text.TextBuilder;

/**
 * @author Savormix
 *
 */
public abstract class DefaultParser implements IParser
{
	/** Should parsers use deterministic analysis */
	public static boolean				ANALYZE_DETERMINISTIC	= true;
	/** Should parsers use experimental analysis */
	public static boolean				ANALYZE_EXPERIMENTAL	= false;
	protected static final CustomLogger	log						= CustomLogger.getLog();
	protected static final long			FIN_WAIT				= 250L;

	protected static final String		EMPTY_STRING			= "";
	protected static final String		LINE_BREAK				= System.getProperty("line.separator");
	protected static final String		COMMENT_SQL				= "-- ";
	protected static final String		COMMENT_PROP			= "# ";
	protected static final String[]		COMMENT_XML				=
																{ "<!-- ", " -->" };
	protected static final char			PAREN_OPEN				= '(';
	protected static final char			PAREN_CLOSE				= ')';
	protected static final char			COMMA					= ',';
	protected static final char			SEMICOLON				= ';';
	protected static final char			QUOTE					= '\'';
	protected static final char			QUOTE2					= '"';
	protected static final char			EQUAL					= '=';
	protected static final char			SPACE					= ' ';
	protected static final char			BRACE_OPEN				= '<';
	protected static final char			BRACE_CLOSE				= '>';
	protected static final char			SLASH					= '/';

	protected final SafelyPackedFile[]	spf;
	protected final int[]				entries;

	protected final Object				finLock					= new Object();
	private int							finCount				= 0;

	protected int[]						order;

	/**
	 * Creates a parser for a given file.
	 * @param spf file contents as a SafelyPackedFile array
	 * @throws IllegalArgumentException too much/not enough files passed
	 */
	public DefaultParser(SafelyPackedFile... spf) throws IllegalArgumentException
	{
		if (spf.length != getNeededFileCount())
			throw new IllegalArgumentException("Cannot create a parser, incorrect amount" + "of files given!");
		this.spf = spf;
		entries = new int[spf.length];
		order = new int[spf.length];
		for (int i = 0; i < spf.length; i++)
		{
			entries[i] = spf[i].readInt();
			order[i] = i;
		}
		arrangeFiles();
	}

	/** Instantiate a parser only for file validation */
	public DefaultParser()
	{
		this.spf = null;
		this.entries = null;
	}

	protected int getArrayIndex(String filename) throws IllegalArgumentException
	{
		for (int i = 0; i < spf.length; i++)
		{
			if (spf[i].getFileName().equalsIgnoreCase(filename))
				return i;
		}
		throw new IllegalArgumentException(filename + " is not a file parsed by this " + "parser!");
	}

	protected void addThread()
	{
		finCount++;
	}

	protected void remThread()
	{
		finCount--;
	}

	protected int getActiveThreads()
	{
		return finCount;
	}

	protected String getChronicleInfo(int cID)
	{
		return "This file is designed to be used with Chronicle " + cID + ": " + Launcher.CHRONICLES[cID] + ".";
	}

	protected void addGenInfo(int cID, TextBuilder sb, String cOpen, String cClose)
	{
		sb.append(cOpen);
		sb.append(VersionService.getGenerator());
		sb.append(cClose);
		sb.append(LINE_BREAK);

		if (cID < 0)
			return;
		sb.append(cOpen);
		sb.append(getChronicleInfo(cID));
		sb.append(cClose);
		sb.append(LINE_BREAK);
	}

	protected void addGenInfo(int cID, TextBuilder sb, String[] comment)
	{
		addGenInfo(cID, sb, comment[0], comment[1]);
	}

	protected void addGenInfo(int cID, TextBuilder sb, String cOpen)
	{
		addGenInfo(cID, sb, cOpen, EMPTY_STRING);
	}

	protected void sqlFinishQuery(TextBuilder sb)
	{
		sb.delete(sb.length() - 1 - LINE_BREAK.length(), sb.length());
		sb.append(SEMICOLON);
		sb.append(LINE_BREAK);
		addGenInfo(-1, sb, COMMENT_SQL);
	}

	private void sqlDeclare(TextBuilder sb, boolean last)
	{
		if (last)
			sb.append(PAREN_CLOSE);
		sb.append(COMMA);
		if (last)
			sb.append(LINE_BREAK);
	}

	protected void sqlDeclare(TextBuilder sb, String s, boolean last)
	{
		sb.append(QUOTE2);
		sb.append(s);
		sb.append(QUOTE2);
		sqlDeclare(sb, last);
	}

	protected void sqlDeclare(TextBuilder sb, long l, boolean last)
	{
		sb.append(l);
		sqlDeclare(sb, last);
	}

	protected void sqlDeclare(TextBuilder sb, String s)
	{
		sqlDeclare(sb, s, false);
	}

	protected void sqlDeclare(TextBuilder sb, long l)
	{
		sqlDeclare(sb, l, false);
	}

	protected void xmlDeclare(TextBuilder tb)
	{
		xmlDeclare(tb, "?xml", new String[]
		{ "version", "encoding" }, new String[]
		{ "1.0", "UTF-8" }, true, 0);
		tb.setCharAt(tb.length() - LINE_BREAK.length() - 2, '?');
	}

	protected void xmlDeclareDoc(TextBuilder tb, String template)
	{
		tb.append("<!DOCTYPE list SYSTEM \"../../templates/");
		tb.append(template);
		tb.append(".dtd\">");
		tb.append(LINE_BREAK);
	}

	protected void xmlDeclare(TextBuilder tb, String element, String[] attribute, Object[] value, boolean complete, int indent)
	{
		xmlIndent(tb, indent);
		tb.append(BRACE_OPEN);
		tb.append(element);
		tb.append(SPACE);
		if (attribute != null && value != null)
		{
			for (int i = 0; i < attribute.length; i++)
			{
				if (value[i] == null || value[i].toString().isEmpty())
					continue;
				tb.append(attribute[i]);
				tb.append(EQUAL);
				tb.append(QUOTE2);
				tb.append(value[i]);
				tb.append(QUOTE2);
				tb.append(SPACE);
			}
		}
		tb.delete(tb.length() - 1, tb.length());
		if (complete)
			tb.append(SLASH);
		tb.append(BRACE_CLOSE);
		tb.append(LINE_BREAK);
	}

	protected void xmlDeclare(TextBuilder tb, String element, boolean complete, int indent)
	{
		xmlDeclare(tb, element, null, null, complete, indent);
	}

	protected void xmlDeclareFinish(TextBuilder tb, String element, int indent)
	{
		xmlIndent(tb, indent);
		tb.append(BRACE_OPEN);
		tb.append(SLASH);
		tb.append(element);
		tb.append(BRACE_CLOSE);
		tb.append(LINE_BREAK);
	}

	protected void xmlComment(TextBuilder tb, String[] lines, int indent)
	{
		if (lines.length > 1)
		{
			xmlIndent(tb, indent);
			tb.append(COMMENT_XML[0]);
			tb.append(LINE_BREAK);
			for (String com : lines)
			{
				xmlIndent(tb, indent + 1);
				tb.append(com);
				tb.append(LINE_BREAK);
			}
			xmlIndent(tb, indent);
			tb.append(COMMENT_XML[1]);
			tb.append(LINE_BREAK);
		}
		else
			xmlComment(tb, lines[0], indent);
	}

	protected void xmlComment(TextBuilder tb, String line, int indent)
	{
		xmlIndent(tb, indent);
		tb.append(COMMENT_XML[0]);
		tb.append(line);
		tb.append(COMMENT_XML[1]);
		tb.append(LINE_BREAK);
	}

	private void xmlIndent(TextBuilder tb, int indent)
	{
		for (int i = 0; i < indent * 2; i++)
			tb.append(SPACE);
	}

	/* =========================================================================== */

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.IParser#arrangeFiles()
	 */
	@Override
	public void arrangeFiles()
	{
		for (int i = 0; i < order.length; i++)
			order[i] = getArrayIndex(getNeededFiles()[i]);
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.IParser#asyncWriteFinalizer()
	 */
	@Override
	public void asyncWriteFinalizer() throws IllegalAccessException
	{
		if (isValidator())
			throw new IllegalAccessException(toString());
		while (getActiveThreads() > 0)
		{
			try
			{
				Thread.sleep(FIN_WAIT);
			}
			catch (InterruptedException ie)
			{
			}
		}
		log.info("Finished parsing " + getParseType() + " data.");
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.IParser#isValidator()
	 */
	@Override
	public boolean isValidator()
	{
		return spf == null;
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.parser.IParser#getNeededFileCount()
	 */
	@Override
	public int getNeededFileCount()
	{
		return getNeededFiles().length;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public final String toString()
	{
		return getParseType() + " parser, validation mode = " + isValidator();
	}
}
